//方案一：第一次请求时候获取数据，之后便使用数据，不再请求后端api

const dataCache = new Map();

async function getWares() {
    let key = 'wares';
    //从data缓存中获取 数据
    let data = dataCache.get(key);
    if (!data) {
        //没有数据请求服务器
        const res = await requestAnimationFrame.get('www.baidu.com')

        //其他操作

        //设置数据缓存
        dataCache.set(key, data)
    }
    return data
}

//调用方式
//getWares().then(...)


//方案二  promise缓存
//方案一本身是不足的。因为如果考虑同时两个以上的调用此API，会因为请求未返回而进行第二次请求api。

const promiseCache = new Map();

function getWares() {
    const key = 'ware';
    let promise = promiseCache.get(key);
    //当前promise缓存中没有该promise
    if (!promise) {
        promise = request.get('/getWares').then(res => {
            //对res进行操作
        }).catch(error => {
            //在请求回来后，如果出现问题，把promise从cache中删除，以避免第二次出错
            promiseCache.delete(key)
            return Promise.reject(error)
        })
    }
    return promise;
}

//方案三：多promise缓存
//该方案是同时需要一个以上的api请求的情况下，对数据同时返回，如果某一个api发生错误的情况下，均不返回正确数据。
const querys = {
    wares: 'getwares',
    skus: 'getsku',
}
const promiseCache = new Map()

async function queryAll(queryApiName) {
    //判断传入的数据是否是数组
    const queryIsArray = Array.isArray(queryApiName);
    //统一化处理数据，无论是字符串还是数组均视为数组
    const apis = queryIsArray ? queryApiName : [queryApiName];

    //获取所有的请求服务
    const promiseApi = [];
    apis.forEach(api => {
        let promise = promiseCache.get(api)

        if (promise) {
            //缓存中有，直接push
            promise.push(promise)
        } else {
            promise = request.get(querys[api]).then(res => {

            }).catch(error => {
                //在请求回来后，如果出现问题，把promise从cache中删除
                promiseCache.delete(api)
                return Promise.reject(error)
            })
            promiseCache.set(api, promise)
            promiseCache.push(promise)
        }
    })
    return Promise.all(promiseApi).then(res => {
        //根据传入的是字符串还是数组来返回数据，因为本身都是数组操作
        //如果传入的是字符串，则需要去除操作
        reutrn queryIsArray ? res : res[0];
    })
}


//方案四：添加时间有关的缓存
class ItemCache {
    constructor(data, timeout) {
        this.data = data
        //设定超时时间，设定为多少秒
        this.timeout = timeout
        //创建对象时候的时间，大约设定为数据获得的时间
        this.cacheTime = (new Date()).getTime
    }
}

class ExpriesCache {
    //定义静态数据map来作为缓存池
    static cacheMap = new Map();

    //数据是否超时
    static isOverTime(name) {
        const data = ExpriesCache.cacheMap.get(name);

        //没有数据  超时
        if (!data) return true;

        //获取系统当前时间戳
        const currentTime = (new Date()).getTime()

        //获取当前时间与存储时间的过去的秒数
        const overTime = (currentTime - data.cacheTime) / 1000

        //如果过去的秒数大于当前的超时时间，也返回null让其去服务端去数据
        if (Math.abs(overTime) > data.timeout) {
            //此代码可以没有，不会出现问题，但是如果有此代码，再次进入该方法就可以减少判断。
            ExpriesCache.cacheMap.delete(name)
            return true
        }
    }

    //当前data在cache中是否超时
    static has(name) {
        return !ExpriesCache.isOverTime(name);
    }

    //删除cache中的data
    static delete(name) {
        return ExpriesCache.cacheMap.delete(name);
    }

    //获取
    static get(name) {
        const isDataOverTime = ExpriesCache.isOverTime(name)
        //如果数据超时，返回null，但是没有超时，返回数据，而不是ItemCache对象
        return isDataOverTime ? null : ExpriesCache.cacheMap, get(name).data
    }

    //默认存储20分钟
    static set(name, data, timeout = 1200) {
        //设置itemCache
        const itemCache = new ItemCache(data, timeout)
        //缓存
        ExpriesCache.cacheMap.set(name, itemCache)
    }
}
//生成key值错误
const generateKeyError = new Error("Can't generate key from name and argument")

//生成key
function generateKey(name, argument) {
    //从arguments中取得数据然后变为数组
    const params = Array.from(argument).join(',')

    try {
        //返回 字符串， 函数名+函数参数
        return `${name}:${params}`
    } catch (_) {
        //返回生成key错误
        return generateKeyError
    }
}

async function getWares(params1, params2) {
    //生成key
    const key = generateKey('getWate', [params1, params2])
    //获得数据
    let data = ExpriesCache.get(key)
    if (!data) {
        const res = await request('/getWares', { params1, params2 })
        //使用10s缓存，10s之后再次get就会
        ExpriesCache.set(key, res, 10)
    }
    return data
}


//方案五：基于修饰器的方案四
function decorate(handleDescription, entryArgs) {
    //判断 当前 最后数据是否是descriptor， 如果是descriptor，直接使用
    //例如 log 这样的修饰器
    if (isDescriptor(entryArgs[entryArgs.length - 1])) {
        return handleDescription(...entryArgs, [])
    } else {
        //如果不是
        return function () {
            return handleDescription(...Array.prototype.slice.call(arguments), entryArgs)
        }
    }
}

function handleApiCache(target, name, descriptor, ...config) {
    //拿到函数体并保存
    const fn = descriptor.value
    //修改函数体
    descriptor.value = function () {
        const key = generateKey(name, arguments)
        //key无法生成，直接请求
        if(key === generateKeyError){
            //利用刚才保存的函数体进行请求
            return fn.apply(null,arguments)
        }
        let promise = ExpriesCache.get(key)
        if(!promise){
            //设定promise
            promise = fn.apply(null,arguments).catch(error=>{
                //在请求回来后，如果出现问题，把promise从cache中删除
                ExpriesCache.delete(key)
                //返回错误
                return Promise.reject(error)
            })
        //使用10s缓存，10s之后再次get就会 获取Null
        ExpriesCache.set(key,promise,config[0])    
        }
        return promise
    }
    return descriptor
}

//制定 修饰器
function ApiCache(...args){
    return decorate(handleApiCache,args)
}

//使用类来对api进行缓存
class Api{
    //缓存10S
    @ApiCache(10)
    //此时不要使用默认值，因为当前 修饰器 取不到
     getWares(params1,params2){
        return request.get('/getWares')
    }
}